package application;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
import java.util.ArrayList;
import java.util.List;

import processes.ProcessDescriptor;
import processes.ProcessesManager;
import analyzator.Analyzator;
import analyzator.AnalyzatorCommands;
import core.PipedInputStreamFileReader;
import core.PipedOutpuStreamFileWriter;

/**
 * This class is the core of the system. Mediates all communication in the system. 
 * Works with manager and processes through reference may write to the graphical interface.
 * Wraps SkyGui. Adds options to manipulate with thread.
 * @author Miroslav Vozabal, Josef Vopalecky
 *
 */
public class Core extends Application {
    static ProcessesManager pdm = new ProcessesManager();
	private Gui gui;
	private Init cShell;
	private Boolean end = false;
	private List<String> commandList;
	private int pointer = 0;
	
	/**
	 * Pointer in list of commands.
	 * @return
	 */
	public int getPointer() {
		return pointer;
	}

	/**
	 * Constructor.
	 */
	public Core(){
		commandList = new ArrayList<String>();
		setPriority(Thread.MAX_PRIORITY);
	}
	/**
	 * Set reference to the core to gui.
	 */
	@Override
	public void code() {
		gui.setCore(this);
		fromGuiShutdown();
	}
	
	/**
	 * It decides whether the process last.
	 * @param id
	 * @return
	 */
	public Boolean lastProccess(int id){
		if(pdm.getTail().getId() == id){
			return true;
		}
		else{
			return false;
		}
	}

	/**
	 * Returns commmand by identifier. 
	 * @param id
	 * @return
	 */
	public String getCommand(int id){
		return commandList.get(id);
	}
	
	/**
	 * Return number command.
	 * @return
	 */
	public int sizeOfListComm(){
		return pointer;
	}
	
	/**
	 * Returns whether the read command.
	 * @param id
	 * @return
	 */
	public Boolean canRead(int id){
		try {
			if(pdm.getProcess(id).getIn() != null){
				if(pdm.getProcess(id).getIn().available() > 0){
					return true;
				}
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			//e.printStackTrace();
		}
		return pdm.getProcess(id).getCanRead();
	}
	
	/**
	 * 
	 * @param id process ID
	 * @return read data from PipedInputStream.
	 */
	public String read(int id){
		BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(pdm.getProcess(id).getIn()));
		String readedString = "";
		try {
			readedString = bufferedReader.readLine();
		} catch (IOException e) {
			//System.out.println("error read");
			readedString = "";
		}
		if(readedString != null){
			//System.out.println("ID: "+ id + " Cte: " + readedString);
		}
		else{
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return readedString;
	}
	
	/**
	 * Change process dir.
	 * @param id
	 * @param s
	 */
	public void changeDir(int id, String s){
		pdm.getProcess(id).getParent().setWorkingDirectory(s);
	}
	
	/**
	 * Return process dir.
	 * @param id
	 * @return
	 */
	public String getDirName(int id){
		return pdm.getProcess(id).getWorkingDirectory();
	}
	
	/**
	 * Kill process by ID.
	 * @param target
	 */
	public void kill(int target){
		if(target == 0){ // 0 is core shell => shutdown()
			shutdown();
		}
		else{
			pdm.killProcess(target);
		}		
	}
	
	/**
	 * 
	 * @return running processes in the system.
	 */
	public String getPS(){
		return pdm.printProcesses();
	}

	/**
	 * Remove last process.
	 */
	public void cleanLastProccess(){
		pdm.cleanLastProccesRefference();
	}
	
	/**
	 * Remove parent process.
	 */
	public void killLastParentProcesses(){
		pdm.killLastParentProcesses();
	}
	
	/**
	 * The method writes text to the PipedOutputStream.
	 * @param id 	process ID
	 * @param text 	text to write
	 */
	public void write(int id, String text){
		try {
			//System.out.println("ID: "+ id + " write: " + text);
			if(!text.endsWith("\n")){
				text = text + "\n";
			}
			pdm.getProcess(id).getOut().write(text.getBytes());
			pdm.getProcess(id).getOut().flush();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}		
	}
	/**
	 * The method is used to get help on a command.
	 * @param help name of commmand
	 */
	public void getHelp(String help){
		HelpBox helpBox = new HelpBox(help, this);
		PipedOutputStream pos = new PipedOutputStream();
		ArrayList<ProcessDescriptor> pdList = new ArrayList<ProcessDescriptor>();
		ProcessDescriptor helpPD = new ProcessDescriptor(new PipedInputStream(), pos, help, "", helpBox);
		pdList.add(helpPD);
		pdm.createProcesses(pdList);
		helpBox.setId(helpPD.getId());
		helpBox.start();
	}
	
	/**
	 * Run commands, creates pipes and processes.
	 * @param comm Name of command
	 * @param id parent
	 */
	public void runCommand(String comm, int id){
		commandList.add(pointer++, comm);
		Analyzator an = new Analyzator(comm);
		ArrayList<ProcessDescriptor> pdList = new ArrayList<ProcessDescriptor>();
		AnalyzatorCommands[] commands = an.getCommands();
		ProcessDescriptor firpd = null;
		ProcessDescriptor fiwpd = null;
		PipedInputStreamFileReader pisr = null;
		PipedOutpuStreamFileWriter posw = null;
		ProcessDescriptor lastPd = null;
		pdm.getProcess(id).setCanRead(false);
		if(an.getInput() != null){
			pisr = new PipedInputStreamFileReader(an.getInput(), this);
			firpd = new ProcessDescriptor(new PipedInputStream(),new PipedOutputStream(),"FileReader","",pisr);
			pdList.add(firpd);
		}
		else{
			try {
				pdm.getProcess(id).getIn().close();
				gui.getPos().close();
				gui.setPos(new PipedOutputStream());
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		for (AnalyzatorCommands command : commands) {
			BlackBox bl = new BlackBox(command.getName(),command.getParameters(), command.getArguments(),this);
			ProcessDescriptor pd = new ProcessDescriptor(new PipedInputStream(), new PipedOutputStream(), command.getName(), "", bl);
			pdList.add(pd);
		}
		if(an.getOutput() != null){
			posw = new PipedOutpuStreamFileWriter(an.getOutput(), this);
			fiwpd = new ProcessDescriptor(new PipedInputStream(), new PipedOutputStream(), "FileWriter", "", posw);
			pdList.add(fiwpd);
		}
		pdm.createProcesses(pdList);
		PipedOutputStream pos;
		if(firpd != null){
			pisr.setID(firpd.getId());
			pos = firpd.getOut();
			lastPd=firpd;
		}
		else{
			pos = gui.getPos();
		}
		for (ProcessDescriptor pd : pdList) {
			if(!pd.getApplicationName().equals("FileReader") && !pd.getApplicationName().equals("FileWriter")){
				BlackBox bl = (BlackBox) pd.getThread();
				try {
					if(pos != null){
						pd.getIn().connect(pos);
						if(lastPd == null){
							gui.setConsument(pd);
						}
						else{
							lastPd.setConsument(pd);
						}
						pd.setCanRead(true);
						lastPd = pd;
					}
					pos = pd.getOut();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				bl.setId(pd.getId());
			}
		}
		if(fiwpd != null){
			posw.setID(fiwpd.getId());
			try {
				fiwpd.getIn().connect(pos);
				fiwpd.setConsument(lastPd);;
				fiwpd.setCanRead(true);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		else{
			try {
				gui.getPis().close();
				gui.setPis(new PipedInputStream());
				gui.getPis().connect(pos);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		//run threads
		if(firpd != null){
			pisr.start();
		}
		for (ProcessDescriptor pd : pdList) {
			if(!pd.getApplicationName().equals("FileReader") && !pd.getApplicationName().equals("FileWriter")){
				BlackBox bl = (BlackBox) pd.getThread();
				bl.start();
			}
		}
		if(fiwpd != null){
			posw.start();
		}
	}
	
	/**
	 *  Close last process.
	 */
	public void closeLastProcess(){
		pdm.getTail().setEnd(true);
	}
	
	/**
	 *  Close parent process
	 */
	
	public void closeParentProcess(){
		pdm.getTail().getParent().setEnd(true);
	}

	
	/**
	 * Connect shell or init with gui.
	 * @param id
	 */
	public void connectToGui(int id){
		try {
			//System.out.println("id " + id + " want connect");
			gui.getPos().close();
			gui.setPos(new PipedOutputStream());
			pdm.getProcess(id).setCanRead(true);
			PipedInputStream pr = new PipedInputStream();
			pr.connect(gui.getPos());
			pdm.getProcess(id).setIn(pr);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/**
	 * Create Init.
	 * @throws IOException
	 */
	public void init() throws IOException{
		ArrayList<ProcessDescriptor> pdList = new ArrayList<ProcessDescriptor>();
		cShell = new Init(this);
		ProcessDescriptor pdcshell =  new ProcessDescriptor(new PipedInputStream(),new PipedOutputStream(), "Init" , System.getProperty("user.dir") ,cShell);
		gui.setConsument(pdcshell);
		pdList.add(pdcshell);
		pdm.createProcesses(pdList);
		cShell.setID(pdcshell.getId());
	}
	
	/**
	 * Close PipedInputStream.
	 * @param id
	 */
	public void closeIn(int id){
		try {
			pdm.getProcess(id).getIn().close();
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/**
	 * Close PipedOutputStream.
	 * @param id
	 */
	public void closeOut(int id){
		try {
			pdm.getProcess(id).getOut().close();
			if(pdm.getProcess(id).getConsument() != null){
				pdm.getProcess(id).getConsument().setCanRead(false);
			}
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/**
	 * Set gui.
	 * @param gui
	 */
	public void setGui(Gui gui){
		this.gui = gui;
	}
	
	/**
	 * Starts init Thread.
	 */
	public void startInit(){
		cShell.start();
	}
	
	/**
	 * OS service - stops processes, gui, core and exits the system.
	 */
	public void shutdown(){
		gui.writeConsole("Aborting the system and processes.");
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		pdm.killProcess(0);
		gui.getOsGui().dispose();
		gui.stop();
		this.stop();		
	}
	
	/**
	 * Test if end.
	 * @param id
	 * @return
	 */
	public Boolean isEnd(int id){
		return pdm.getProcess(id).getEnd();
	}
	
	/**
	 * Return a reference to the gui.
	 * @return
	 */
	public Gui getGui(){
		return gui;
	}	
	
	/**
	 * Arranges shutdown of OS from Gui.
	 */
	private void fromGuiShutdown() {
		gui.getOsGui().getShutDownItem().addActionListener(new ActionListener() {			
			public void actionPerformed(ActionEvent arg0) {
				shutdown();
			}
		});
	}
}
